package org.hgf.testrabbitmq.producer.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import javax.annotation.Resource;

/**
 * 生产者队列配置
 *
 * @author 何国发
 * @version 1.0.0
 * @date 2022/9/6 19:00
 */
@Slf4j
@Configuration
public class ProducerRabbitMqConfig {

    /**
     * 缓存连接工厂
     */
    @Resource
    private CachingConnectionFactory connectionFactory;

    /**
     * 注入和配置rabbitmq消息模板
     * Scope("prototype")：每次使用都会返回一个新的对象
     *
     * @return org.springframework.amqp.rabbit.core.RabbitTemplate
     * @author 何国发
     * @date 2022/9/8 9:54
     */
    @Bean
    @Scope("prototype")
    public RabbitTemplate rabbitTemplate() {
        /*
         * 1. 若使用confirm-callback或return-callback，必须要配置publisherConfirms或publisherReturns为true
         * 2. 每个rabbitTemplate只能有一个confirm-callback和return-callback，如果这里配置了，那么写生产者的时候不能再写confirm-callback和return-callback
         * 3. 使用return-callback时必须设置mandatory为true，或者在配置中设置mandatory-expression的值为true
         */
        /*
         * 考虑到并发性，这里不使用 confirm 模式 和 return 模式
         * 如果使用这两个模式的话，会报异常 channelMax reached。（这种情况一般出现于大数据量的情况，如果需要的话请增大rabbitmq.conf文件中的channel_max的值）
         * 如果后边需要这两个模式的话，再做解决，可以考虑通过Thread.sleep()（让已有连接休眠一段时间） 的方式，减少 channel 的积压
         */
        connectionFactory.setPublisherConfirms(false);
        connectionFactory.setPublisherReturns(false);

        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 设置转换器
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
// ------------------------- 如果需要使用confirm 模式 和 return 模式请修改上面的两个false为true ----------------------------------------------------------------
        // 正常情况以下代码均不会生效
        // Mandatory：为true时,消息通过交换器无法匹配到队列会返回给生产者 并触发MessageReturn，为false时,匹配不到会直接被丢弃
        rabbitTemplate.setMandatory(true);

        /*
         * confirm 模式 和 return 模式回调处理
         * 如果消息没有到exchange，则confirm回调,ack=false
         * 如果消息到达exchange，则confirm回调,ack=true
         * exchange到queue成功，则不回调return
         * exchange到queue失败，则回调return（需设置mandatory=true,否则不回回调,消息就丢了）
         */
        rabbitTemplate.setConfirmCallback(((correlationData, ack, cause) -> {
            if (ack)
            {
                log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, true, cause);
            } else {
                log.error("消息发送失败:correlationData({}),ack({}),cause({})", correlationData, false, cause);
            }
        }));
        rabbitTemplate.setReturnCallback(
                (message, replyCode, replyText, exchange, routingKey)
                        -> log.debug("消息丢失：交换机(exchange)【{}】, 路由键(routeKey)【{}】, 响应代码(replyCode)【{}】, 响应消息(replyText)【{}】, 要发送的消息(message)【{}】", exchange, routingKey, replyCode, replyText, message));

        return rabbitTemplate;
    }

    /**
     * 将这个类交给spring管理
     *
     * @return org.springframework.amqp.rabbit.core.RabbitAdmin
     * @author 何国发
     * @date 2022/9/8 10:00
     */
    @Bean
    public RabbitAdmin rabbitAdmin() {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);

        return rabbitAdmin;
    }

}
